/*
 * Copyright 2013 Robert Peszek.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fpig.lists.functions

import fpig.util.CallUtil
import fpig.util.FunUtil;
import fpig.common.Nothing;
import fpig.common.functions.FpigBase;
import fpig.lists.structures.FunList
import fpig.lists.functions.impl.*

/**
 * Basic List functions
 *
 * http://code.google.com/p/fpiglet/
 * @author Robert Peszek
 */
class Base {
	
	/**
	 * Returns Empty List
	 * Logical signature
	 * 
	 * isEmpty ::  -> []
	 * @return
	 */
	static Closure empty =  { -> FunList.EMPTYLIST}
	
	/**
	 * Verifies if list is emtpy
	 * Logical signature
	 * 
	 * isEmpty :: [a] -> boolean
	 * 
	 */
	static Closure isEmpty = CallUtil.toFunction {FunList l -> l.empty}

	/**
	 * Returns list head. Throws runtime exception (EmptyListException) if called on empty list.
	 * headM alternative returns Maybe
	 *
	 * head :: [a] -> a
	 *
	 */
	static Closure head = CallUtil.toFunction {FunList l -> 
		if(l.empty)
		    throw new EmptyListException("no head on empty list")
		else 
			l.head
	}
	
	/**
	 * Returns list head. Throws runtime exception (EmptyListException) if called on empty list.
	 * headM alternative returns Maybe
	 *
	 * head :: [a] -> Maybe a
	 *
	 */
	static Closure headM = CallUtil.toFunction {FunList l ->
		if(l.empty)
			FpigBase.nothing()
		else
			FpigBase.just(l.head)
	}

	
	/**
	 * Returns list tail
	 *
	 * head :: [a] -> [a]
	 *
	 */
	static Closure tail = CallUtil.toFunction {FunList l -> l.tail }
	
	
	/**
	 * Returns length of finite list
	 * Logical signature:
	 *
	 *  reduceXR :: [a] -> long
	 *
	 */
	static Closure length = Length.length
	
	/**
	 * Returns list from n to m, this assumes only that m implements comparedTo so that m<n resolves to false and n implements next() so ++n resolves to increamented values
	 * Logical signature:
	 *
	 *  range :: (a implements compareTo and next) =>  a -> a -> [a]
	 *
	 */
	static Closure range = Range.range

	
	
	/**
	 * Returns concatenated list, expects list of lists (either Groovy List of FunLists or FunList of FunLists).
	 * Logical signature:
	 *
	 *  range ::  [[a]] -> [a]
	 *
	 */
	static Closure concat = Concat.concat
    /**
	 * Standard functional fold-left.
	 * Logical signature:
	 *  
	 *  foldL :: (a -> b -> a) -> a -> [b] -> a
	 *  
	 *  Stack Overflow safe. Implementation uses trampoline.
	 *  Will not work with infinite lists (logical limitation).
	 */
	static Closure foldL = FoldL.foldL

	/**
	 * Folds until stopping rule is met or end of the list.
	 * Logical signature:
	 *
	 *  foldLUntil :: (a->boolean) -> (a -> b -> a) -> a -> [b] -> a
	 *
	 *  Stack Overflow safe. Implementation uses trampoline.
	 *  Will not work with infinite lists (logical limitation).
	 */
	static Closure foldLUntil = FoldL.foldLUntil

	/**
	 * Same as foldL only initialized with list head.
	 * Logical signature:
	 *  
	 *  reduceL :: (a -> b -> a) -> [b] -> a
	 * 
	 */
	static Closure reduceL = FoldL.reduceL

	/**
	 * Version of right fold for finite lists.
	 * Logical signature:
	 * 
	 * foldR :: (b -> a -> a) -> a -> [b] -> a
	 * 
	 * This is not an equivalent of foldL << reverse!
	 * Implemented by looking at foldR as chained function composition.
	 * Will stack overflow. 
	 */
	static Closure foldR = FoldR.foldR

	/**
	 * Same as foldR, only does not require initialization.
	 * Logical signature:
	 *  
	 *  reduceR :: (b -> a -> a) -> [b] -> a
	 * 
	 */
	static Closure reduceR = FoldR.reduceR

	/**
	 * Version of fold which works for infinite lists
	 * Logical signature:
	 * 
	 * 	foldXR :: (b -> (_->a) -> a) -> a -> [b] -> a
     *
	 * Implemented by looking at foldR as chained function composition.
	 * The trick is that the accumulator needs to be a closure to allow for function composition 
	 * chain to terminate.
	 * Will stack overflow. 
	 */
	static Closure foldXR = FoldXR.foldXR

	/**
	 * Same as foldR only initialized with list head.
	 * Logical signature:
	 *  
	 *  reduceXR :: (b -> (_->a) -> a) -> [b] -> a
	 * 
	 */
	static Closure reduceXR = FoldXR.reduceXR


	/**
	 * Standard filter function
	 * Logical signature:
	 * 
	 * filter :: (a->boolean) -> [a] -> [a]
	 * 
	 *  Stack Overflow safe. Implementation uses combination of lazy recursion and trampolines.
	 *  Works on infinite lists
	 */
	static Closure filter = Filter.filter


	/**
	 * Standard map function
	 * Logical signature
	 * 
	 * map :: (a->b) -> [a] -> [b]
	 * 
	 * Stack Overflow safe. Implementation uses lazy recursion
	 * Works on infinite lists
	 */
	static Closure map = Map.map

	
	/**
	* Standard take function
	* Logical signature
	*
	* take :: Number -> [a] -> [a]
	*
	* Stack Overflow safe. Implementation uses lazy recursion
	* Works on infinite lists
	*/
    static Closure take = Take.take

	/**
	* Standard takeWhile function
	* Think of it as filter with exit condition.
	* Logical signature
	*
	* takeWhile :: (a->boolean) -> [a] -> [a]
	*
	* Stack Overflow safe. Implementation uses lazy recursion
	* Works on infinite lists
	*/
	static Closure takeWhile = Take.takeWhile

	/**
	* Standard drop function
	* Logical signature
	*
	* map :: Number -> [a] -> [a]
	*
	* Stack Overflow safe. Implementation uses trampoline
	* Works on infinite lists
	*/
	static Closure drop = Drop.drop

	/**
	* Standard dropWhile function
	* Logical signature
	*
	* map :: (a->boolean) -> [a] -> [a]
	*
	* Stack Overflow safe. Implementation uses trampoline
	* Works on infinite lists
	*/
	static Closure dropWhile = Drop.dropWhile

	/**
	 * Standard prepend function
	 * Logical signature:
	 * 
	 * prepend :: a -> [a] -> [a]
	 * 
	 * Stack overflow safe
	 * Works on infinite lists
	 */
	static Closure prepend = CallUtil.toFunction {a, FunList l -> 	
		l.build(a, { l })
	}

	//Derived Methods
	/** 
	 * Element function supports prepending with <<
	 * Example:
	 * 
	 * e(0) << e(1) << e(2) << FunList.EMPTYLIST //creates equivalent of [0,1,2]
	 * 
	 * Logical signature:
	 * 
	 * e :: a -> [a] -> [a]
	 */
	static Closure e = prepend
	
	/**
	 * Reverse function (reverses order of elements in the list)
	 * 
	 * Logical signature
	 * 
	 * reverse :: [a] -> [a]
	 * 
	 * Stack overflow safe
	 * Obviously, will not work on infinite lists.
	 */
	static Closure reverse = foldL(CallUtil.flip(prepend), FunList.EMPTYLIST)
	
	/**
	 * zipWith function, accepts a function of 2 arguments and 2 lists, returns list with the function applied
	 * to 'running' arguments from both lists.
	 * 
	 * Logical Signature
	 * 
	 * zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
	 */
	static Closure zipWith = Zip.zipWith
}
